package com.ansion.utils.file;

import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ansion.string.StringUtils;
import com.ansion.log.VLog;
import com.ansion.utils.CloseUtils;
import com.ansion.utils.ResourceUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Locale;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@SuppressWarnings({"UnusedReturnValue", "unused"})
public class FileUtils {
    public static final long SIZE_KB_1024 = 1024;
    public static final long SIZE_MB_1024 = 1024 * 1024L;
    public static final long SIZE_GB_1024 = 1024 * 1024 * 1024L;

    private static final double SIZE_KB = 1024;
    private static final double SIZE_MB = SIZE_KB * 1024;
    private static final double SIZE_G = SIZE_MB * 1024;
    private static final double SIZE_10M = 10 * SIZE_MB;
    private static final double SIZE_5M = 5 * SIZE_MB;

    public static final String UNKNOWN = "unknown";

    public static String getFilePathExt(String name) {
        String extension = null;
        if (!TextUtils.isEmpty(name)) {
            int index = name.lastIndexOf('/');
            if (index >= 0) {
                String temp = name.substring(index + 1);
                if (temp.contains(".")) {
                    extension = temp.substring(temp.lastIndexOf('.') + 1);
                }
            }
        }
        return extension;
    }

    public static String getFileExt(String name) {
        String extension = null;
        if (!TextUtils.isEmpty(name) && name.contains(".")) {
            extension = name.substring(name.lastIndexOf('.') + 1);
        }
        return extension;
    }

    public static String extensionName(String name) {
        if (name == null) {
            return UNKNOWN;
        }
        int index = name.lastIndexOf(".");
        int endIndex = name.length();
        if (index >= 0 && index + 1 < endIndex &&
                StringUtils.isDigitsOnly(name, index + 1, endIndex)) {
            // skip digits suffix
            endIndex = index;
            index = name.lastIndexOf('.', index - 1);
        }
        if (index < 0) {
            index = name.lastIndexOf("/");
            if (index >= 0 && index + 1 < endIndex &&
                    StringUtils.isDigitsOnly(name, index + 1, endIndex)) {
                // skip digits suffix
                endIndex = index;
                index = name.lastIndexOf('/', index - 1);
            }
        }
        if (index < 0) {
            return UNKNOWN;
        }
        return name.substring(index + 1, endIndex).toLowerCase();
    }

    public static String getFileExtension(String localPath) {
        if (TextUtils.isEmpty(localPath)) {
            return "";
        }
        File file = new File(localPath);
        if (!file.exists() || file.isDirectory()) {
            return "";
        }
        try {
            Uri uri = SFileProvider.getUriForFile(new File(localPath));
            return MimeTypeMap.getSingleton().getExtensionFromMimeType(ResourceUtils.getContentResolver().getType(uri));
        } catch (Throwable ignore) {
            return extensionName(file.getName());
        }
    }

    public static boolean isUnknownType(String format) {
        return TextUtils.isEmpty(format) || UNKNOWN.equals(format) || "null".equals(format);
    }

    public static String extensionNameRaw(String name) {
        if (name == null) {
            return UNKNOWN;
        }
        int index = name.lastIndexOf(".");
        int endIndex = name.length();
        if (index < 0 || index + 1 == endIndex) return "";
        return name.substring(index + 1, endIndex).toLowerCase();
    }

    public static long fileSize(String path) {
        File f = new File(path);
        if (f.exists() && f.isFile()) {
            return f.length();
        }
        return 0;
    }

    public static String sizeString(long byteSize) {
        if (byteSize < SIZE_KB) { // B
            return byteSize + " B";
        }
        if (byteSize < SIZE_MB) { // KB
            return (int) (byteSize / SIZE_KB) + " KB";
        }
        if (byteSize < SIZE_G) { // MB
            double f = (byteSize / SIZE_MB);
            return String.format(Locale.ENGLISH, "%.1f MB", f);
        }
        double f = byteSize / SIZE_G;
        return String.format(Locale.ENGLISH, "%.01f GB", f);
    }

    public static String sizeStringIn1024(long bytes) {
        if (bytes < SIZE_KB_1024) {
            return bytes + " B";
        } else if (bytes < SIZE_MB_1024) {
            return (bytes / SIZE_KB_1024) + " KB";
        } else if (bytes < SIZE_GB_1024) {
            double f = bytes / (double) SIZE_MB_1024;
            return String.format(Locale.ENGLISH, "%.1f MB", f);
        } else {
            double f = bytes / (double) SIZE_GB_1024;
            return String.format(Locale.ENGLISH, "%.01f GB", f);
        }
    }

    public static boolean isFileExists(String path) {
        if (TextUtils.isEmpty(path)) {
            return false;
        }

        File file = new File(path);
        return file.exists() && file.isFile();
    }

    public static boolean isFileExistsAndNotEmpty(String path) {
        if (TextUtils.isEmpty(path)) {
            return false;
        }

        File file = new File(path);
        return file.exists() && file.isFile() && file.length() > 0;
    }

    public static boolean isFileValid(String path) {
        if (TextUtils.isEmpty(path)) {
            return false;
        }

        File file = new File(path);
        return file.exists() && file.isFile() && file.length() > 0;
    }

    public static boolean isOver1G(String path) {
        if (TextUtils.isEmpty(path)) {
            return false;
        }

        File f = new File(path);
        if (f.exists() && f.isFile()) {
            return f.length() > SIZE_G;
        }
        return false;
    }

    public static boolean isOver10M(String path) {
        if (TextUtils.isEmpty(path)) {
            return false;
        }

        File f = new File(path);
        if (f.exists() && f.isFile()) {
            return f.length() > SIZE_10M;
        }
        return false;
    }

    public static boolean isOver5M(String path) {
        if (TextUtils.isEmpty(path)) {
            return false;
        }

        File f = new File(path);
        if (f.exists() && f.isFile()) {
            return f.length() > SIZE_5M;
        }
        return false;
    }

    public static boolean isOver10M(byte[] arr) {
        if (arr == null) {
            return false;
        }
        return arr.length > SIZE_10M;
    }

    public static void delete(@NonNull File root) {
        if (!root.exists()) {
            return;
        }

        if (root.isFile()) {
            if (!root.delete()) {
                VLog.e("TAG_BASE_MISC", "Failed to delete file: %s", root.getAbsoluteFile());
            }
        } else if (root.isDirectory()) {
            File[] subFiles = root.listFiles();
            if (subFiles == null || subFiles.length == 0) {
                return;
            }
            for (File f : subFiles) {
                delete(f);
            }
            if (!root.delete()) {
                VLog.e("TAG_BASE_MISC", "Failed to delete the dir: %s", root.getAbsoluteFile());
            }
        } else {
            VLog.w("TAG_BASE_MISC", "Skip to delete unknown file: %s", root.getAbsoluteFile());
        }
    }

    public static boolean ensureDirectory(@NonNull String dirPath) {
        try {
            final File dir = new File(dirPath);
            if (dir.exists() && !dir.isDirectory()) {
                delete(dir);
            }
            if (!dir.exists() && dir.mkdirs()) {
                //hide our medias from other application
                File fileNoMedia = new File(dir, ".nomedia");
                if (!fileNoMedia.exists()) {
                    if (!fileNoMedia.createNewFile()) {
                        VLog.w("%s Failed to create [%s]", "TAG_BASE_MISC", fileNoMedia.getAbsoluteFile());
                    }
                }
            }
            return dir.exists() && dir.isDirectory();
        } catch (Exception e) {
            return false;
        }
    }

    public static boolean ensureDirectoryOnly(@NonNull String dirPath) {
        try {
            final File dir = new File(dirPath);
            if (dir.exists() && !dir.isDirectory()) {
                delete(dir);
            }
            if (!dir.exists() && dir.mkdirs()) {
            }
            return dir.exists() && dir.isDirectory();
        } catch (Exception e) {
            return false;
        }
    }

    public static void copyDirectory(@NonNull File sourceDir, @NonNull File targetDir) throws IOException {
        copyDirectory(sourceDir, targetDir, new DefaultFileStreamProvider());
    }

    public static void copyDirectory(@NonNull File sourceDir, @NonNull File targetDir,
                                     @NonNull FileStreamProvider streamProvider) throws IOException {
        File[] filesList = sourceDir.listFiles();
        if (filesList == null || filesList.length == 0) {
            return;
        }

        //noinspection ResultOfMethodCallIgnored
        targetDir.mkdirs();

        for (File file : filesList) {
            File targetFile = new File(targetDir, file.getName());
            if (file.isFile()) {
                copyFile(file, targetFile, streamProvider);
            } else if (file.isDirectory()) {
                copyDirectory(file, targetFile, streamProvider);
            }
        }
    }

    public static boolean copyFileByPathAndName(String oldPath$Name, String newPath$Name) {
        try {
            File oldFile = new File(oldPath$Name);
            if (!oldFile.exists() || !oldFile.isFile() || !oldFile.canRead()) {
                return false;
            }

            FileInputStream fileInputStream = new FileInputStream(oldPath$Name);
            FileOutputStream fileOutputStream = new FileOutputStream(newPath$Name);
            byte[] buffer = new byte[(int) SIZE_KB_1024];
            int byteRead;
            while ((byteRead = fileInputStream.read(buffer)) != -1) {
                fileOutputStream.write(buffer, 0, byteRead);
            }
            fileInputStream.close();
            fileOutputStream.flush();
            fileOutputStream.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    public static void traverseDirectory(@NonNull File root, @NonNull LongHolder sizeHolder,
                                         @NonNull IntegerHolder countHolder) {
        if (!root.exists()) {
            return;
        }

        if (root.isFile()) {
            sizeHolder.value += root.length();
            countHolder.value++;
        } else if (root.isDirectory()) {
            File[] subFiles = root.listFiles();
            if (subFiles == null || subFiles.length == 0) {
                return;
            }
            for (File f : subFiles) {
                traverseDirectory(f, sizeHolder, countHolder);
            }
        } else {
            VLog.w("TAG_BASE_MISC", "Unhandled file: %s", root.getAbsoluteFile());
        }
    }

    public static void copyFile(@NonNull File sourceFile, @NonNull File targetFile) throws IOException {
        copyFile(sourceFile, targetFile, new DefaultFileStreamProvider());
    }

    /***
     * 拷贝文件到输出流中
     * @param out
     * @throws IOException
     */
    public static void copyFile(File file, OutputStream out) throws IOException {
        BufferedInputStream inBuff = null;
        BufferedOutputStream outBuff = null;
        try {
            // 新建文件输入流并对它进行缓冲
            inBuff = new BufferedInputStream(new FileInputStream(file));
            // 新建文件输出流并对它进行缓冲
            outBuff = new BufferedOutputStream(out);
            // 缓冲数组
            byte[] b = new byte[1024 * 5];
            int len;
            while ((len = inBuff.read(b)) != -1) {
                outBuff.write(b, 0, len);
            }
            // 刷新此缓冲的输出流
            outBuff.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            if (inBuff != null)
                inBuff.close();
            if (outBuff != null)
                outBuff.close();
        }


    }

    public static void copyFile(String path, OutputStream out) throws IOException {
        File file = new File(path);
        copyFile(file, out);
    }


    public static void copyFile(@NonNull File sourceFile, @NonNull File targetFile,
                                @NonNull FileStreamProvider provider) throws IOException {
        InputStream sourceStream = null;
        OutputStream targetStream = null;
        try {
            sourceStream = provider.openForInput(sourceFile);
            targetStream = provider.openForOutput(targetFile);
            if (sourceStream != null && targetStream != null) {
                StreamUtils.copyStream(sourceStream, targetStream);
            }
//            VLog.i(TAG_CHAT_FILE + TAG_SEND_FILE + "copy file success:" + sourceFile.getAbsolutePath() + "  to :" + targetFile.getAbsolutePath());
        } catch (Throwable e) {
//            VLog.e(e, TAG_CHAT_FILE + TAG_SEND_FILE + "copy file failed!");
        } finally {
            CloseUtils.silentlyClose(sourceStream);
            CloseUtils.silentlyClose(targetStream);
        }
    }

    public static boolean copyFileQuietly(@NonNull File sourceFile, @NonNull File targetFile) {
        return copyFileQuietly(sourceFile, targetFile, new DefaultFileStreamProvider());
    }

    public static boolean copyFileQuietly(@NonNull File sourceFile, @NonNull File targetFile,
                                          @NonNull FileStreamProvider provider) {
        try {
            copyFile(sourceFile, targetFile, provider);
            return true;
        } catch (IOException e) {
//            VLog.w(e, "%s failed to copy file", "TAG_BASE_MISC");
            return false;
        }
    }

    public static void saveFile(@NonNull File file, @NonNull byte[] content,
                                boolean append) throws IOException {
        try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file, append))) {
            out.write(content);
            out.flush();
        }
    }

    public static boolean saveFileQuietly(@NonNull File file, @NonNull byte[] content, boolean append) {
        try {
            saveFile(file, content, append);
            return true;
        } catch (IOException e) {
//            VLog.w(e, "%s Failed to save file: %s", "TAG_BASE_MISC", file.getAbsoluteFile());
        }
        return false;
    }

    @Nullable
    public static byte[] readFile(@NonNull File file) throws IOException {
        if (!file.exists() || !file.isFile()) {
            return null;
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream((int) file.length());
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {
            StreamUtils.copyStream(in, out);
            return out.toByteArray();
        } finally {
            CloseUtils.silentlyClose(out);
        }
    }

    @Nullable
    public static byte[] readFileQuietly(@NonNull File file) {
        try {
            return readFile(file);
        } catch (IOException e) {
//            VLog.w(e, "%s Failed to read file content: %s", "TAG_BASE_MISC", file.getAbsoluteFile());
        }
        return null;
    }

    /**
     * Delete directory and files under directory
     *
     * @param root
     */
    public static void deleteDirectory(@NonNull File root) {
        if (!root.exists() || !root.isDirectory()) {
            VLog.w("%s Skip to delete unknown directory: %s", "TAG_BASE_MISC", root.getAbsoluteFile());
            return;
        }

        File[] subFiles = root.listFiles();
        if (subFiles == null || subFiles.length == 0) {
            return;
        }
        for (File f : subFiles) {
            delete(f);
        }

        root.delete();
    }

    /**
     * Only delete all the files in the folder, not the directory
     *
     * @param directory
     */
    public static void deleteFilesByDirectory(File directory) {
        if (directory != null && directory.exists() && directory.isDirectory()) {
            for (File item : directory.listFiles()) {
                item.delete();
            }
        }
    }


    public static byte[] file2bytes(String filePath) {
        if (TextUtils.isEmpty(filePath)) {
            return null;
        }

        File file = new File(filePath);
        return file2bytes(file);
    }

    public static byte[] file2bytes(File file) {
        byte[] buffer = null;
        if (file == null || !file.exists() || file.isDirectory()) {
//            VLog.logI("file don't exist or is dic!");
            return null;
        }
        FileInputStream fis = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            fis = new FileInputStream(file);
            byte[] b = new byte[1024];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            buffer = bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            CloseUtils.silentlyClose(fis);
            CloseUtils.silentlyClose(bos);
        }
        return buffer;
    }

    public static String getFileName(String path) {
        if (path == null) {
            return null;
        }

        String fileName = null;
//        String path = uri.getPath();
        int cut = path.lastIndexOf('/');
        if (cut != -1) {
            fileName = path.substring(cut + 1);
        }
        return fileName;
    }

    public static void deleteDir(@NonNull File dir) {
        try {
            StorageUtils.deleteDirectory(dir, false);
        } catch (SecurityException e) {
//            VLog.logE("failed to delete dir", e);
        }
    }

    public static void deleteDir(@NonNull File dir, boolean fileOnly) {
        try {
            StorageUtils.deleteDirectory(dir, fileOnly);
        } catch (SecurityException e) {
//            VLog.logE("failed to delete dir", e);
        }
    }

    public static File makeRandomDir(@NonNull File parentDir) {
        File dir = new File(parentDir, UUID.randomUUID().toString().replace("-", ""));
        boolean makeOk;
        try {
            makeOk = dir.mkdir();
        } catch (SecurityException e) {
//            VLog.logE("failed to make dir", e);
            makeOk = false;
        }
        return makeOk ? dir : null;
    }

    /**
     * @param path
     * @return
     */
    public static File ensureFileExists(String path) throws IOException {
        File tmpFile = new File(path);
        return ensureFileExists(tmpFile);
    }

    public static File ensureFileExists(File tmpFile) throws IOException {
        if (tmpFile.isDirectory()) {
            throw new IOException(tmpFile.getAbsolutePath() + " is a directory");
        }
        if (!tmpFile.exists()) {
            tmpFile.createNewFile();
        }
        return tmpFile;
    }

    /**
     * InputStream -> File，InputStream 转文件，会写入到文件后面
     *
     * @param inputStream
     * @param file
     * @throws IOException
     */
    public static void copyInputStreamToFile(InputStream inputStream, File file) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            int read;
            byte[] bytes = new byte[1024];
            while ((read = inputStream.read(bytes)) != -1) {
                fos.write(bytes, 0, read);
            }
            fos.flush();
        } catch (IOException e) {
            VLog.e("copyInputStreamToFile file:%s fail:%s", file.getAbsolutePath(), e.getMessage());
        } finally {
            CloseUtils.silentlyClose(inputStream);
            CloseUtils.silentlyClose(fos);
        }
    }

    public static void cleanDirectory(File directory) throws IOException {
        String message;
        if (!directory.exists()) {
            message = directory + " does not exist";
            throw new FileNotFoundException(message);
        } else if (!directory.isDirectory()) {
            message = directory + " is not a directory";
            throw new IOException(message);
        } else {
            File[] files = directory.listFiles();
            if (files == null) {
                throw new IOException("Failed to list contents of " + directory);
            } else {
                File[] var2 = files;
                int var3 = files.length;

                for (int var4 = 0; var4 < var3; ++var4) {
                    File file = var2[var4];
                    forceDelete(file);
                }

            }
        }
    }

    public static void forceDelete(File file) throws IOException {
        if (file.isDirectory()) {
            cleanDirectory(file);
        }

        if (file.exists() && !file.delete()) {
            String message = "Unable to delete file: " + file.getName();
            throw new IOException(message);
        }
    }

    public static File createFile(String filePath) {
        File file = new File(filePath);
        if (file.exists()) {
            return file;
        }
        if (filePath.endsWith(File.separator)) {// 以 路径分隔符 结束，说明是文件夹
            return null;
        }

        //判断父目录是否存在
        if (!file.getParentFile().exists()) {
            //父目录不存在 创建父目录
            if (!file.getParentFile().mkdirs()) {
                return null;
            }
        }

        //创建目标文件
        try {
            if (file.createNewFile()) {//创建文件成功
                return file;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        return null;
    }


    public static boolean copyUriToFile(@NonNull Context ctx, @NonNull Uri uri, @NonNull File file) {
        FileInputStream is = null;
        FileOutputStream fos = null;
        try {
            ContentResolver cr = ctx.getContentResolver();
            // 使用ContentResolver的openFileDescriptor方法获取ParcelFileDescriptor对象
            ParcelFileDescriptor fd = cr.openFileDescriptor(uri, "r");
            // 使用ParcelFileDescriptor的getFileDescriptor方法获取FileDescriptor对象
            // 利用FileDescriptor对象建立文件输入流(FileInputStream)
            is = new FileInputStream(fd.getFileDescriptor());

            fos = new FileOutputStream(file);
            byte[] b = new byte[1024];
            int read = 0;
            while ((read = is.read(b)) != -1) {
                fos.write(b, 0, read);
            }
            fos.flush();
        } catch (Throwable e) {
//            VLog.e("%s Receiving Send Parse fileProvider error,uri:%s,error:%s ", TAG_START_UP, uri, e.getMessage());
//            SnackbarUtil.show(mActivity, R.string.message_toast_message_send_failed, Snackbar.LENGTH_LONG);
            return false;
        } finally {
            CloseUtils.silentlyClose(is);
            CloseUtils.silentlyClose(fos);
        }
        return true;
    }

    public static void createPathIfNeed(String path) {
        try {
            File tf = new File(path);
            if (!tf.exists()) {
                tf.mkdirs();
            }
        } catch (Exception e) {
            VLog.e(e, "file", "create file dir fail");
        }

    }

    /**
     * 压缩文件
     *
     * @param zipOutputSteam
     * @throws Exception
     */
    public static void ZipOnlyFile(String filePath, ZipOutputStream zipOutputSteam,
                                   FileStreamProvider provider) throws IOException {
        if (zipOutputSteam == null)
            return;
        File file = new File(filePath);
        InputStream fis = null;
        try {
            if (file.isFile()) {
                fis = provider.openForInput(file);
                if (fis == null) {
                    return;
                }
                ZipEntry zipEntry = new ZipEntry(file.getName());
                zipOutputSteam.putNextEntry(zipEntry);

                int len;
                byte[] buffer = new byte[4096];
                while ((len = fis.read(buffer)) != -1) {
                    zipOutputSteam.write(buffer, 0, len);
                }
                zipOutputSteam.closeEntry();
            }
        } finally {
            CloseUtils.silentlyClose(fis);
        }
    }

    public static void zipFolder(ZipOutputStream outZip, String srcFilePath, FileStreamProvider provider)throws Exception {
        if (outZip == null)
            return;
        //打开要输出的文件
        File file = new File(srcFilePath);

        //压缩
        zipFiles(file.getParent()+ File.separator, file.getName(), outZip, provider);

    }

    private static void zipFiles(String folderPath, String filePath,
                                 ZipOutputStream zipOut, FileStreamProvider provider)throws Exception{
        if(zipOut == null){
            return;
        }

        File file = new File(folderPath+filePath);
        InputStream fis = null;
        //判断是不是文件
        if (file.isFile()) {
            fis = provider.openForInput(file);
            if (fis == null) {
                return;
            }
            ZipEntry zipEntry =  new ZipEntry(filePath);
            zipOut.putNextEntry(zipEntry);

            int len;
            byte[] buffer = new byte[4096];

            while((len=fis.read(buffer)) != -1) {
                zipOut.write(buffer, 0, len);
            }

            zipOut.closeEntry();
        } else {
            //文件夹的方式,获取文件夹下的子文件
            String fileList[] = file.list();

            //如果没有子文件, 则添加进去即可
            if (fileList.length <= 0) {
                ZipEntry zipEntry =
                        new ZipEntry(filePath+ File.separator);
                zipOut.putNextEntry(zipEntry);
                zipOut.closeEntry();
            }

            //如果有子文件, 遍历子文件
            for (int i = 0; i < fileList.length; i++) {
                zipFiles(folderPath, filePath+ File.separator+fileList[i], zipOut, provider);
            }//end of for

        }//end of if

    }
}
